local listener = DoTimer;

local types = {
    [DoTimer.HELPFUL_TYPE] = "Helpful",
    [DoTimer.HARMFUL_TYPE] = "Harmful",
};

function listener:FindAnchor(tarName, timerName, tarGUID, filter, isMine)
    --[[
        Logic:
            When deciding if it's correct based on moving targets, if .timers has any entries then they are treated as a whitelist.  
            Hierarchy:
                1. based on target name; current target/focus
                2. based on target name; standard target
                3. based on timer name
                4. based on target name; PB or NT
                5. based on default location
    --]]
    timerName = string.lower(timerName);
    local isPB = tarName == "Party Buffs";
    local isNT = tarName == "No Target";
    local isDoT = filter == self.HARMFUL_TYPE;
    local isHoT = filter == self.HELPFUL_TYPE;
    tarName = string.lower(tarName);
    local isStandard = not isPB and not isNT;
    local isFocus = isStandard and tarGUID == UnitGUID("focus");
    local isTarget = isStandard and tarGUID == UnitGUID("target");
    local otherLoc, specificLocT, specificLocF, specificLocN, targetLoc, focusLoc, standardLoc, timerLoc, dotLoc, hotLoc, nonStandardLoc, defaultLoc;
    
    for anchor, settings in pairs(self:Get("userAnchors")) do
        if not isMine and settings.otherLoc then otherLoc = anchor; end
        if settings.specificTimers then
            local allowed = false;
            if settings.timers then
                for query in pairs(settings.timers) do
                    if string.find(timerName, query) then
                        allowed = anchor;
                        break;
                    end
                end
            end
            if settings.targets and allowed then
                if isTarget and settings.targets["*target*"] then
                    specificLocT = anchor;
                elseif isFocus and settings.targets["*focus*"] then
                    specificLocF = anchor;
                elseif isStandard and settings.targets[tarName] then
                    specificLocN = anchor;
                end
            end
        else
            if settings.timers then
                for query in pairs(settings.timers) do
                    if string.find(timerName, query) then
                        timerLoc = anchor;
                    end
                end
            end
            if settings.targets then
                if isTarget and settings.targets["*target*"] then
                    targetLoc = anchor;
                elseif isFocus and settings.targets["*focus*"] then
                    focusLoc = anchor;
                elseif isStandard and settings.targets[tarName] then
                    standardLoc = anchor;
                end
            end
        end
        if isPB and settings.pbLoc then nonStandardLoc = anchor; end
        if isNT and settings.ntLoc then nonStandardLoc = anchor; end
        if isDoT and settings.dotLoc then dotLoc = anchor; end
        if isHoT and settings.hotLoc  then hotLoc = anchor; end
        if settings.default then defaultLoc = anchor; end
    end
    
    local movedTimer = (not (targetLoc or focusLoc or standardLoc) and timerLoc) and true or false;
    return otherLoc or specificLocT or specificLocF or specificLocN or targetLoc or focusLoc or standardLoc or timerLoc or nonStandardLoc or dotLoc or hotLoc or defaultLoc or nil, movedTimer;
end

function listener:FindTimerSettings(name, anchor, tarName, isMine)
    name = string.lower(name);
    if isMine then
        local settings = self:Get("userAnchors", anchor);
        if settings.timerSettings then
            for setting, entries in pairs(settings.timerSettings) do
                for entry in pairs(entries) do
                    if string.find(name, entry) then
                        return setting;
                    end
                end
            end
        end
    end
    local setting = "standardSetting";
    if tarName == "No Target" then
        setting = "ntSetting";
    elseif tarName == "Party Buffs" then
        setting = "pbSetting";
    elseif not isMine then
        setting = "otherSetting";
    end
    return self:Get(setting);
end

function listener:MakeTimer(target, timer)
    --self:Print(string.format("New Timer: %s on %s!", timer.name, target.name));
    local isHidden = timer.module == self and (self:Get("blocked", string.lower(timer.name)) or self:Get("blockedTypes", types[timer.type]));
    local priority = timer.module == self and 0 or 10;
    local tPriority, tHidden = self:GetPriority(target);
    local anchor, movedTimer = self:FindAnchor(target.name, timer.name, target.uniqueID, timer.type, timer.isMine);
    local timerSettings;
    if timer.module == self then
        timerSettings = self:FindTimerSettings(timer.name, anchor, target.name, timer.isMine);
    else
        timerSettings = timer.module.GetTimerSettings and timer.module:GetTimerSettings(self, timer.name) or nil;
    end
    --local group = target.name == "No Target" and self:Get("ntName") or target.name == "Party Buffs" and self:Get("pbName") or movedTimer and self:Get("standardName") or target;
    --local group = (target.name == "No Target" or target.name == "Party Buffs") and "Important" or target;
    local group;
    if movedTimer then
        group = self:Get("userAnchors", anchor).preserveTarget and target or self:Get("standardName");
    else
        group = target.name == "No Target" and self:Get("ntName") or target.name == "Party Buffs" and self:Get("pbName") or target;
    end
    if type(group) == "string" then
        tPriority, tHidden = 10, nil;
    end
    local tID, gID = TimerLib:NewTimer(timer, group, timer.module, timerSettings, priority, isHidden, anchor, tPriority, tHidden);
    timer.id = tID;
    if group == target then
        timer.groupID = gID;
    end
end

function listener:RemoveTimer(target, timer)
    --self:Print(string.format("Timer Done: %s on %s!", timer.name, target.name));
    TimerLib:DeleteTimer(timer.id, timer, (timer.module ~= self));
end

function listener:UpdateTargetData(target)
    --self:Print(string.format("The target data for %s has changed!", target.name));
    for timer in target:GetTimers() do
        if timer.groupID then
            TimerLib:UpdateGroup(timer.groupID, target);
        end
    end
end

function listener:UpdateTimerData(timer)
    --self:Print(string.format("The timer data of %s on %s has changed!", timer.name, timer.target.name));
    TimerLib:UpdateTimer(timer.id, timer);
    if timer.nameChanged then
        timer.nameChanged = false;
        self:UpdateAnchor(timer);
    end
end

function listener:GetPriority(target)
    local tarID, focID = UnitGUID("target"), UnitGUID("focus");
    local sT, sF, soT, soF = self:Get("sortTarget"), self:Get("sortFocus"), self:Get("showOnlyTarget"), self:Get("showOnlyFocus");
    local always = self:Get("always");
    
    local hidden = nil;
    if soT and soF and (tarID or focID or always) and not (target.uniqueID == tarID or target.uniqueID == focID) then
        hidden = true;
    elseif soT and (tarID or always) and target.uniqueID ~= tarID then
        hidden = true;
    elseif soF and (focID or always) and target.uniqueID ~= focID then
        hidden = true;
    end
    
    local priority = 2;
    if sT and target.uniqueID == tarID then
        priority = 0;
    elseif sF and target.uniqueID == focID then
        priority = 1;
    elseif target.name == "No Target" then
        priority = 3;
        hidden = nil;
    elseif target.name == "Party Buffs" then
        priority = 4;
        hidden = nil;
    end
    
    return priority, hidden;
end

function listener:UpdateAllPriorities()
    for _, target in DoTimer:GetTargets() do
        local priority, hidden = self:GetPriority(target);
        local group = (target.name == "No Target" or target.name == "Party Buffs") and target.name or target;
        for timer in target:GetTimers() do
            if timer.groupID then
                TimerLib:UpdateGroupInfo(timer.groupID, group, priority, hidden);
            end
        end
    end
end

listener:SetScript("OnTimerEnter", function(self, target, timer, id, frame)
    if not timer then return; end
    local point = "ANCHOR_RIGHT";
    local counter = 5;
    repeat
        local SpellLib = AsheylaLib:GetLibrary("SpellLib");
        target = timer.target;
        Ash_GameTooltip:SetOwner(frame, point);
        Ash_GameTooltip:ClearLines();
        local time = GetTime();
        
        if target.name == "Party Buffs" then
            Ash_GameTooltip:AddLine("Filter: |cff00ff00" .. timer.partyBuffType);
            
            Ash_GameTooltip:AddLine("Active Spells:");
            local t = listener:AcquireTable();
            for guid, info in pairs(timer.affecting) do
                t[info.name] = 1;
            end
            for spell in pairs(t) do
                Ash_GameTooltip:AddLine("   |cff00ffff" .. spell);
            end
            listener:ReleaseTable(t);
            
            Ash_GameTooltip:AddLine("Active On:");
            for guid, info in pairs(timer.affecting) do
                Ash_GameTooltip:AddLine(string.format("   |cffffffff%s|r (|cff6666ff%s|r)", info.person, id:FormatTime(info.duration - time + info.time)));
            end
            
            local displayedHeader = false;
            for guid, info in pairs(timer.notAffecting) do
                if not displayedHeader then
                    Ash_GameTooltip:AddLine("Not Active On:");
                    displayedHeader = true;
                end
                Ash_GameTooltip:AddLine("   |cffffffff" .. info.person);
            end
        else
            if timer.foundTooltip == nil then
                timer.foundTooltip = false;
                local _, book, index = SpellLib:FindSpellInfo(timer.name, timer.rank, "spell");
                if book and index then
                    timer.foundTooltip = true;
                    timer.tooltipType = "spell";
                    timer.tooltipBook = book;
                    timer.tooltipIndex = index;
                else
                    local _, _, itemID = SpellLib:FindSpellInfo(timer.name, nil, "item");
                    if itemID then
                        timer.foundTooltip = true;
                        timer.tooltipType = "item";
                        timer.tooltipItemID = itemID;
                    end
                end
            end
            
            if timer.foundTooltip then
                if timer.tooltipType == "spell" then
                    Ash_GameTooltip:SetSpell(timer.tooltipIndex, timer.tooltipBook);
                else
                    Ash_GameTooltip:SetHyperlink(SpellLib:ReturnItemLink(timer.tooltipItemID));
                end
                Ash_GameTooltip:AddLine(" ");
            end
            
            Ash_GameTooltip:AddLine(string.format("|cff00ff00%s|r %s", timer.name, timer.rank and "(|cff00ffff" .. timer.rank .. "|r)" or ""));
            if target.name ~= "No Target" then
                Ash_GameTooltip:AddLine(string.format("On: |cffffffff[%s] %s", target.level == -1 and "??" or target.level, target.name)); 
            end
            Ash_GameTooltip:AddLine("Remaining: |cff6666ff" .. id:FormatTime(timer.duration - time + timer.time));
            
            Ash_GameTooltip:AddLine(" ");
            Ash_GameTooltip:AddLine("|cffffffff" .. listener:GetKeyBinding("Timer", "Announce") .. "|r to announce.");
            Ash_GameTooltip:AddLine("|cffffffff" .. listener:GetKeyBinding("Timer", "Remove") .. "|r to remove.");
            Ash_GameTooltip:AddLine("|cffffffff" .. listener:GetKeyBinding("Timer", "Block") .. "|r to block.");
        end
        
        Ash_GameTooltip:Show();
        if Ash_GameTooltip:GetWidth() >= UIParent:GetRight() - frame:GetRight() then
            if point == "ANCHOR_LEFT" then
                break;
            else
                point = "ANCHOR_LEFT";
            end
        else   
            break;
        end
        counter = counter - 1;
    until counter <= 0;
    if (counter <= 0) then print("DoT fail"); end
end)

listener:SetScript("OnTimerLeave", function(self, target, timer, id, frame)
    Ash_GameTooltip:Hide();
end)

listener:SetScript("OnTimerClick",function(self, target, timer, id, button)
    if timer then
        if timer.target.name == "Party Buffs" then
            AsheylaLib:AlertUser("Party Buffs timers are non-interactable.");
        else
            listener:ProcessClick("Timer", button, timer, id);
        end
    else
        TimerLib:DeleteTimer(id, timer, 1);
    end
end)
listener:SetClickAction("Timer", "Remove", function(timer, id)
    if timer.totemID then
        DestroyTotem(timer.totemID);
    else
        TimerLib:DeleteTimer(id, timer, 1);
    end
end)
listener:SetClickAction("Timer", "Block", function(timer)
    if timer then
        if timer.isMine then
            local frame = StaticPopup_Show("DOTIMER", timer.name)
            frame.data = timer.name;
            frame.data2 = listener;
        else
            local frame = StaticPopup_Show("DOTIMER2", timer.name)
            frame.data = timer.name;
            frame.data2 = listener;
        end
    end
end)
local subTarget, subTimer, subID;
local raidIcons = {
    "star", "circle", "diamond", "triangle", "moon", "square", "cross", "skull"
};
local subFunc = function(a)
    if a == "%s" then
        return subTimer.name;
    elseif a == "%d" then
        return subID:FormatTime(subTimer.duration - GetTime() + subTimer.time);
    elseif a == "%l" then
        return (subTarget.level == -1 and "??" or subTarget.level);
    elseif a == "%r" then
        return (not subTarget.icon or subTarget.icon == 0) and "" or "{" .. raidIcons[subTarget.icon] .. "}";
    elseif a == "%t" then
        return subTarget.name;
    else
        return "";
    end
end
listener:SetClickAction("Timer", "Announce", function(timer, id)
    if listener:Get("isAnnouncing") then
        local target = timer.target;
        local subStr = target.name == "No Target" and listener:Get("noTargetAnnounce") or listener:Get("standardAnnounce");
        subTarget, subTimer, subID = target, timer, id;
        local msg = string.gsub(subStr, "(%%%a)", subFunc);
        local loc = GetNumRaidMembers() > 0 and "RAID" or GetNumPartyMembers() > 0 and "PARTY" or "SAY";
        SendChatMessage(msg, loc);
    end
end)
StaticPopupDialogs["DOTIMER"] = {
    text = "Are you sure you want to block |cff00ff00%s|r?  This can be undone from the options panel.",
    button1 = "Yes",
    button2 = "No",
    OnAccept = function(self, name, module) module:Set("blocked", string.lower(name), 1); end,
    whileDead = 1,
    hideOnEscape = 1,
    timeout = 0,
}
StaticPopupDialogs["DOTIMER2"] = {
    text = "Are you sure you want to stop seeing others' |cff00ff00%s|r?  This can be redone in the options panel.",
    button1 = "Yes",
    button2 = "No",
    OnAccept = function(self, name, module) module:Set("otherTimers", string.lower(name), nil); end,
    whileDead = 1,
    hideOnEscape = 1,
    timeout = 0,
}

local f = CreateFrame("Frame");
f:RegisterEvent("PLAYER_TARGET_CHANGED");
f:RegisterEvent("PLAYER_FOCUS_CHANGED");
f:SetScript("OnEvent", function(self, event)
    DoTimer:UpdateTimerAnchors();
end);

local defaultSettings = {
    sortTarget = false;
    sortFocus = false;
    showOnlyTarget = false;
    showOnlyFocus = false;
	keybindings = {},
    userAnchors = {},
    blocked = {},
    blockedTypes = {},
    isAnnouncing = true,
    pbName = "Party Buffs",
    ntName = "No Target",
    standardName = "DoTimer",
    standardSetting = nil,
    ntSetting = nil,
    pbSetting = nil,
    otherSetting = nil,
    noTargetAnnounce = "My %s will expire in %d.",
    standardAnnounce = "My %s on %r%t will expire in %d.",
};
listener:AddDefaultSettings(defaultSettings);
listener:AddSettingsUpdateScript(function(self)
    if self:GetUserSet("keybindings") == nil then
        self:Set("keybindings", {
    		["Timer"] = {
    			["Announce"] = "1",
    			["Remove"] = "2",
    			["Block"] = "s-2",
    		},
    	});
    end
    if self:GetUserSet("userAnchors") == nil then
        self:Set("userAnchors", {
            ["DoTimer Anchor"] = {
                default = true,
                targets = {},
                timerSettings = {},
            },
        });
    end
    if self:Get("enabled") then
        self:RegisterEvent("DOTIMER_NEW_TIMER", self.MakeTimer);
        self:RegisterEvent("DOTIMER_DONE_TIMER", self.RemoveTimer);
        self:RegisterEvent("DOTIMER_UPDATE_TARGETDATA", self.UpdateTargetData);
        self:RegisterEvent("DOTIMER_UPDATE_TIMERDATA", self.UpdateTimerData);
        self:UpdateTimerAnchors();
    else
        self:UnregisterEvent("DOTIMER_NEW_TIMER");
        self:UnregisterEvent("DOTIMER_DONE_TIMER");
        self:UnregisterEvent("DOTIMER_UPDATE_TARGETDATA");
        self:UnregisterEvent("DOTIMER_UPDATE_TIMERDATA");
        TimerLib:DeleteAllTimers(self);
    end
end);

function listener:UpdateAnchor(timer)
    local target = timer.target;
    local tPriority, tHidden = self:GetPriority(target);
    local anchor, movedTimer = self:FindAnchor(target.name, timer.name, target.uniqueID, timer.type, timer.isMine);
    local isHidden = timer.module == self and (self:Get("blocked", string.lower(timer.name)) or self:Get("blockedTypes", types[timer.type]));
    local priority = timer.module == self and 0 or 10;
    local timerSettings = self:FindTimerSettings(timer.name, anchor, target.name, timer.isMine);
    TimerLib:UpdateTimerInfo(timer.id, timer, priority, isHidden, timerSettings);
    --local group = target.name == "No Target" and self:Get("ntName") or target.name == "Party Buffs" and self:Get("pbName") or movedTimer and self:Get("standardName") or target;
    local group;
    if movedTimer then
        group = self:Get("userAnchors", anchor).preserveTarget and target or self:Get("standardName");
    else
        group = target.name == "No Target" and self:Get("ntName") or target.name == "Party Buffs" and self:Get("pbName") or target;
    end
    local newGroupID = TimerLib:MoveTimer(timer.id, timer, group, anchor, type(group) ~= "string" and tPriority or 10, type(group) ~= "string" and tHidden or nil);
    if group == target then
        timer.groupID = newGroupID;
        TimerLib:UpdateGroupInfo(newGroupID, target, tPriority, tHidden);
    end
end

function listener:UpdateTimerAnchors()
    for i, target in self:GetTargets() do
        for timer in target:GetTimers() do
            self:UpdateAnchor(timer);
        end
    end
end

TimerLib:AddSettingsUpdateScript(function(self)
    if not self:GetUserSet("anchorSettings", "DoTimer Anchor") then
        self:Set("anchorSettings", "DoTimer Anchor", {defaultTimerSetting = "DoTimer Timer"});
    end
    if not self:GetUserSet("timerSettings", "DoTimer Timer") then
        self:Set("timerSettings", "DoTimer Timer", {});
    end
    
    local default;
    for name, settings in pairs(DoTimer:Get("userAnchors")) do
        if not self:Get("anchorSettings", name) then
            DoTimer:Set("userAnchors", name, nil);
        elseif settings.default then
            default = name;
        end
    end
    if not default then
        DoTimer:Set("userAnchors", "DoTimer Anchor", "default", true);
    end
end);
